#include <cassert>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <cstdio>

#include "uarch/enums.h"
#include "sim_stat_manager.h"

extern Simulator Sim;

using namespace std;

Sim_stat_manager::Sim_stat_manager(sim_output_mode_t outputfmt = OUTPUT_FMT_COUT):
	output_dest(outputfmt),
	report_filenames(NULL),
	dir_name(NULL),
	output_stream(NULL),
	x_dimension(Sim.settings.mesh_x_dimension),
	y_dimension(Sim.settings.mesh_y_dimension),
	simulator_stats(NULL),
	//global_processor_stat_engine(),
	global_L1_stat_engine(),
	global_L2_stat_engine(),
	global_L3_stat_engine(),
	global_memory_controller_stat_engine()
//	global_mesh_interface_stat_engine(),
//	global_router_stat_engine()
{

	switch(outputfmt) {
	case OUTPUT_FMT_COUT:
		output_stream = &cout;
		break;
	case OUTPUT_FMT_CERR:
		output_stream = &cerr;
		break;
	case OUTPUT_FMT_CSV:
		init_filenames();
		break;
	case OUTPUT_FMT_NONE:
		break;
	default:
		assert(0 && "Invalid output destination");
	}
}

Sim_stat_manager::~Sim_stat_manager()
{
	delete dir_name;
	delete [] report_filenames;
}

void Sim_stat_manager::output_stats()
{
	assert( output_dest != OUTPUT_FMT_NONE);

	for (int i = 0; i < Sim.settings.num_nodes; i++)
	{
		if (output_dest == OUTPUT_FMT_CSV)
		{
			output_stream = new ofstream( report_filenames[i].c_str(), ios::out);
		}
		output_node_stats(i);

		if (output_dest == OUTPUT_FMT_CSV)
		{
			((ofstream*)output_stream)->close();
			delete output_stream;
		}
	}
}

void Sim_stat_manager::output_node_stats(int node)
{
	*output_stream << "Node " << node << " Stats" << endl;

    //MAP<int, Processor_stat_engine*>::iterator it_pr;
	MAP<int, Hash_table_stat_engine*>::iterator it_ht;
    MAP<int, Memory_controller_stat_engine*>::iterator it_mc;
	MAP<int, Mesh_interface_stat_engine*>::iterator it_ni;
    MAP<int, Router_stat_engine*>::iterator it_r;

    //it_pr = processor_stat_engine_map.find(node);
	//if (it_pr != processor_stat_engine_map.end())
	//{
	//	processor_stat_engine_map[node]->print_stats(*output_stream);
	//}
    
	it_ht = L1_stat_engine_map.find(node);
	if (it_ht != L1_stat_engine_map.end())
	{
		L1_stat_engine_map[node]->print_stats(*output_stream);
	}

	it_ht = L2_stat_engine_map.find(node);
	if (it_ht != L2_stat_engine_map.end())
	{
		L2_stat_engine_map[node]->print_stats(*output_stream);
	}

	it_ht = L3_stat_engine_map.find(node);
	if (it_ht != L3_stat_engine_map.end())
	{
		L3_stat_engine_map[node]->print_stats(*output_stream);
	}
    
    it_mc = memory_controller_stat_engine_map.find(node);
	if (it_mc != memory_controller_stat_engine_map.end())
	{
		memory_controller_stat_engine_map[node]->print_stats(*output_stream);
	}

//    it_ni = mesh_interface_stat_engine_map.find(node);
//	if (it_ni != mesh_interface_stat_engine_map.end())
//	{
//		mesh_interface_stat_engine_map[node]->print_stats(*output_stream);
//	}

//    it_r = router_stat_engine_map.find(node);
//	if (it_r != router_stat_engine_map.end())
//	{
//		router_stat_engine_map[node]->print_stats(*output_stream);
//	}

}

void Sim_stat_manager::output_all()
{
	output_stats();
	output_global_stats();
}

void Sim_stat_manager::output_global_stats()
{
    char filename[200];
    static int dump_count = 0;

    merge_all();
    if (output_dest == OUTPUT_FMT_CSV)
    {
        sprintf (filename,"%s/system_summary_%d.csv", dir_name->c_str(), dump_count++);
        output_stream = new ofstream (filename, ios::out );
    }

    *output_stream << "*** Global Stats" << endl;
    simulator_stats->print_stats (*output_stream);
    simulator_stats->clear_stats ();

    //*output_stream << "*** Global Processor" << endl;
    //global_processor_stat_engine.print_stats (*output_stream);
    //global_processor_stat_engine.clear_stats ();

    *output_stream << "*** Global L1" << endl;
    global_L1_stat_engine.print_stats (*output_stream);
    global_L1_stat_engine.clear_stats ();

    *output_stream << "*** Global L2" << endl;
    global_L2_stat_engine.print_stats (*output_stream);
    global_L2_stat_engine.clear_stats ();

//    *output_stream << "*** Global L3" << endl;
//    global_L3_stat_engine.print_stats (*output_stream);
//    global_L3_stat_engine.clear_stats ();

    *output_stream << "*** Global Memory Controller" << endl;
    global_memory_controller_stat_engine.print_stats (*output_stream);
    global_memory_controller_stat_engine.clear_stats ();

//	*output_stream << "*** Global Mesh Interface" << endl;
//	global_mesh_interface_stat_engine.print_stats (*output_stream);
//	global_mesh_interface_stat_engine.clear_stats ();

//    *output_stream << "*** Global Router" << endl;
//    global_router_stat_engine.print_stats (*output_stream);
//    global_router_stat_engine.clear_stats ();

    /** 
     *output_stream << "*** Global Predictor" << endl;
     global_predictor_stat_engine.print_stats (*output_stream);
     global_predictor_stat_engine.clear_stats ();

     *output_stream << "*** Global Remap Table" << endl;
     global_remap_table_stat_engine.print_stats (*output_stream);
     global_remap_table_stat_engine.clear_stats ();

    */

//    output_router_heat_maps (*output_stream);

    if (output_dest == OUTPUT_FMT_CSV)
    {
        ((ofstream*)output_stream)->close();
        delete output_stream;
    }
}

void Sim_stat_manager::save_stats()
{
	//TODO: delete this?
}

//void Sim_stat_manager::add_processor_engine(int id, Processor_stat_engine * engine)
//{
//	processor_stat_engine_map[id] = engine;
//}

void Sim_stat_manager::add_L1_engine(int id, Hash_table_stat_engine * engine)
{
	L1_stat_engine_map[id] = engine;
}

void Sim_stat_manager::add_L2_engine(int id, Hash_table_stat_engine * engine)
{
	L2_stat_engine_map[id] = engine;
}

void Sim_stat_manager::add_L3_engine(int id, Hash_table_stat_engine * engine)
{
	L3_stat_engine_map[id] = engine;
}

void Sim_stat_manager::add_memory_controller_engine(int id, Memory_controller_stat_engine * engine)
{
	memory_controller_stat_engine_map[id] = engine;
}

//void Sim_stat_manager::add_mesh_interface_stat_engine(int id, Mesh_interface_stat_engine * engine)
//{
//	mesh_interface_stat_engine_map[id] = engine;
//}

//void Sim_stat_manager::add_router_stat_engine(int id, Router_stat_engine * engine)
//{
//	router_stat_engine_map[id] = engine;
//}

void Sim_stat_manager::add_simulator_stat_engine(Simulator_stat_engine * engine)
{
	simulator_stats = engine;
}

void Sim_stat_manager::merge_all()
{
	//merge_processor_stats();
	merge_L1_stats();
	merge_L2_stats();
	merge_L3_stats();
	merge_memory_ctrl_stats();
	//merge_mesh_stats();
}

//void Sim_stat_manager::merge_processor_stats()
//{
//	MAP<int, Processor_stat_engine*>::iterator iter;
//	for(iter = processor_stat_engine_map.begin(); iter != processor_stat_engine_map.end(); iter++)
//	{
//		(*iter).second->global_stat_merge(&global_processor_stat_engine);
//	}
//}

void Sim_stat_manager::merge_L1_stats()
{
	MAP<int, Hash_table_stat_engine*>::iterator iter;
	for(iter = L1_stat_engine_map.begin(); iter != L1_stat_engine_map.end(); iter++)
	{
		(*iter).second->global_stat_merge(&global_L1_stat_engine);
	}
}

void Sim_stat_manager::merge_L2_stats()
{
	MAP<int, Hash_table_stat_engine*>::iterator iter;
	for(iter = L2_stat_engine_map.begin(); iter != L2_stat_engine_map.end(); iter++)
	{
		(*iter).second->global_stat_merge(&global_L2_stat_engine);
	}
}

void Sim_stat_manager::merge_L3_stats()
{
	MAP<int, Hash_table_stat_engine*>::iterator iter;
	for(iter = L3_stat_engine_map.begin(); iter != L3_stat_engine_map.end(); iter++)
	{
		(*iter).second->global_stat_merge(&global_L3_stat_engine);
	}
}

void Sim_stat_manager::merge_memory_ctrl_stats()
{
	MAP<int, Memory_controller_stat_engine*>::iterator iter;
	for(iter = memory_controller_stat_engine_map.begin(); iter != memory_controller_stat_engine_map.end(); iter++)
	{
		(*iter).second->global_stat_merge(&global_memory_controller_stat_engine);
	}
}

//void Sim_stat_manager::merge_mesh_stats()
//{
//	MAP<int, Mesh_interface_stat_engine*>::iterator iter;
//	for(iter = mesh_interface_stat_engine_map.begin(); iter != mesh_interface_stat_engine_map.end(); iter++)
//	{
//		(*iter).second->global_stat_merge(&global_mesh_interface_stat_engine);
//	}
//}

//void Sim_stat_manager::merge_router_stats()
//{
//	MAP<int, Router_stat_engine*>::iterator iter;
//	for(iter = router_stat_engine_map.begin(); iter != router_stat_engine_map.end(); iter++)
//	{
//		(*iter).second->global_stat_merge(&global_router_stat_engine);
//	}
//}

void Sim_stat_manager::clear_all()
{
	//clear_processor_stats();
	clear_L1_stats();
	clear_L2_stats();
	clear_L3_stats();
	clear_memory_ctrl_stats();
//	clear_mesh_stats();
}

//void Sim_stat_manager::clear_processor_stats()
//{
//	MAP<int, Processor_stat_engine*>::iterator iter;
//	for(iter = processor_stat_engine_map.begin(); iter != processor_stat_engine_map.end(); iter++)
//	{
//		(*iter).second->clear_stats();
//	}
//}

void Sim_stat_manager::clear_L1_stats()
{
	MAP<int, Hash_table_stat_engine*>::iterator iter;
	for(iter = L1_stat_engine_map.begin(); iter != L1_stat_engine_map.end(); iter++)
	{
		(*iter).second->clear_stats();
	}
}

void Sim_stat_manager::clear_L2_stats()
{
	MAP<int, Hash_table_stat_engine*>::iterator iter;
	for(iter = L2_stat_engine_map.begin(); iter != L2_stat_engine_map.end(); iter++)
	{
		(*iter).second->clear_stats();
	}
}

void Sim_stat_manager::clear_L3_stats()
{
	MAP<int, Hash_table_stat_engine*>::iterator iter;
	for(iter = L3_stat_engine_map.begin(); iter != L3_stat_engine_map.end(); iter++)
	{
		(*iter).second->clear_stats();
	}
}

void Sim_stat_manager::clear_memory_ctrl_stats()
{
	MAP<int, Memory_controller_stat_engine*>::iterator iter;
	for(iter = memory_controller_stat_engine_map.begin(); iter != memory_controller_stat_engine_map.end(); iter++)
	{
		(*iter).second->clear_stats();
	}
}

//void Sim_stat_manager::clear_mesh_stats()
//{
//	MAP<int, Mesh_interface_stat_engine*>::iterator iter;
//	for(iter = mesh_interface_stat_engine_map.begin(); iter != mesh_interface_stat_engine_map.end(); iter++)
//	{
//		(*iter).second->clear_stats();
//	}
//}

//TODO a heatmap abstraction would be good, but for right now, there's only
// one heatmap in the system
#if 0
void Sim_stat_manager::output_router_heat_maps(ostream & out)
{
	/* allocate space to hold the heat map data */
    double sum;
	double ** temp = new double* [y_dimension];
	for (int i = 0; i < y_dimension; i++)
	{
		temp[i] = new double[x_dimension];
	}
    sum = 0;

	/* get the total count information */
	MAP<int, Router_stat_engine*>::iterator iter;

	/* total count for each histogram */
	for (iter = router_stat_engine_map.begin();
         iter != router_stat_engine_map.end();
         iter++)
	{
		int x = (*iter).second->my_router->x_coor;
		int y = (*iter).second->my_router->y_coor;

		temp[y][x] = (*iter).second->router_stall_hist.total;
	}

	out << "Packet count per router heat-map" << endl;
	for (int i = 0; i < y_dimension; i++)
	{
		for (int j = 0; j < x_dimension; j++)
		{
			out << temp[i][j] << ",";
            sum += temp[i][j];
		}
		out << endl;
	}

	out << "Global packet count," << sum << endl;
	out << "Packets per node count," << sum / Sim.settings.num_nodes << endl;

	/* averages for each histogram */
	for (iter = router_stat_engine_map.begin();
         iter != router_stat_engine_map.end();
         iter++ )
	{
		int x = (*iter).second->my_router->x_coor;
		int y = (*iter).second->my_router->y_coor;

		temp[y][x] = (*iter).second->router_stall_hist.get_average();
	}
	out << "Average packet delay per router heat-map" << endl;
	for (int i = 0; i < y_dimension; i++)
	{
		for (int j = 0; j < x_dimension; j++)
		{
			out << temp[i][j] << ",";
		}
		out << endl;
	}

	/* standard deviation for each histogram */
	for (iter = router_stat_engine_map.begin();
		iter != router_stat_engine_map.end();
		iter++ )
	{
		int x = (*iter).second->my_router->x_coor;
		int y = (*iter).second->my_router->y_coor;

		temp[y][x] = (*iter).second->router_stall_hist.get_pop_std_dev();
	}
	out << "Std_dev packet delay per router heat-map" << endl;
	for (int i = 0; i < y_dimension; i++)
	{
		for (int j = 0; j < x_dimension; j++)
		{
			out << temp[i][j] << ",";
		}
		out << endl;
	}

	/* delete the temporary array */
	for (int i = 0; i < y_dimension; i++)
	{
		delete [] temp[i];
	}
	delete [] temp;
}
#endif

/*
 * Initialize the filenames
 */
void Sim_stat_manager::init_filenames()
{
    time_t rawtime;
    struct tm *timeinfo;

    char *name;
    char my_directory[200];
    char filename[200];

    assert(output_dest != OUTPUT_FMT_NONE);

    time (&rawtime);
    timeinfo = localtime (&rawtime);

    if ((name = getenv ("OUTPUT_DIR")) != NULL)
    {
        strcpy (my_directory, name);
    }
    else
    {
        sprintf (my_directory,"%d_%d_%d_%d%d%d",
                 timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_year+1900,
                 timeinfo->tm_hour,timeinfo->tm_min, timeinfo->tm_sec);
        setenv ("OUTPUT_DIR", my_directory, 1);
    }

    mkdir (my_directory, S_IRWXU | S_IRWXG | S_IRWXO);
    dir_name = new string((const char*)my_directory);

    report_filenames = new string[Sim.settings.num_nodes];

    for (int i = 0; i < Sim.settings.num_nodes; i++)
    {
        sprintf(filename,"%s/node%03d.csv", dir_name->c_str(), i);
        report_filenames[i] = (const char *)filename;
    }
}
